package org.hilo.boot.app.component;

import java.io.ByteArrayOutputStream;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.DispatcherType;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import org.hilo.boot.core.UT;

/**
 * http request 信息探测与日志打印
 * 
 * Spring如何打印 入参出参信息（包括@RequestBody、@ResponseBody的文本数据）？

    理论上可以在Spring某个地方进行扩展和配置。但是Spring的处理机制太过于复杂，很难找到一个合适的地方。
    
    经过深入研究，初步方案如下：
    1、必须在Spring解析body参数的地方，重写一个方法，可以让参数解析失败或成功时，记录或打印入参信息。
    2、但是有些请求，可能都到达不了解析body的地方就已经报错返回了。
    2、出参则简单一点，只需要解析返回对象和异常即可，但是必须在其他解析器和拦截器处理完之后。
    
    由于现实难度太大，故放弃。
    
    这个类（ControllerRequestLogInterceptor）只用于调试时使用。
 * 
 * @author zollty
 * @since 2020年10月28日
 */
@Component
@ConditionalOnProperty(name = "hilo.web.requestlog", havingValue = "true")
public class ControllerRequestLogInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerFactory.getLogger("reqLog");
    private static final String ignoreUrlRegex = "(^/(public|webjars|static)/.*)|(.*[.](html|css|js|png|jpg|gif|jsp)$)";
//            ".*((/public/)|(/webjars)|(/static/.*)|([.]((html)|(jsp)|(css)|(js)|(gif)|(png)|(jpg))))$";

    private RequestBodyParser bodyParser = new RequestBodyParser();
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        
        System.out.println("----------------------------------");
        System.out.println("--------" + request.getRequestURI());
        System.out.println("----------------------------------");

        DispatcherType dispatcherType = request.getDispatcherType();
        
        if (DispatcherType.REQUEST.equals(dispatcherType)
                || DispatcherType.ASYNC.equals(dispatcherType)) { // 只解析原始请求
            
            // 请求html页面、js不打印日志
            boolean ignore = request.getRequestURI().matches(ignoreUrlRegex);
            if (isAjaxRequest(request) && !ignore) {
                try {
                    if (logger.isDebugEnabled()) {
                        logger.debug(requestInfo(request, response, handler));
                    }
                } catch (Exception e) {
                    logger.debug("warning:", e);
                }
            }
        }

        return true;
    }
    

    public String requestInfo(HttpServletRequest request, HttpServletResponse response,
            Object handler) {

        if (!(handler instanceof HandlerMethod)) {
            return null;
        }
        
        StringBuilder sb = new StringBuilder(1024);
        sb.append("-----------------------").append(UT.Date.format_yyyy_MM_dd_HH_mm_ss(new Date()))
                .append("-------------------------------------\n");
        HandlerMethod h = (HandlerMethod) handler;
        // 方法名称
//        sb.append("Method    : ").append(h.getMethod().getName()).append("\n");
        // 请求方式 post\put\get 等等
//        sb.append("RequestMethod: ").append(request.getMethod()).append("\n");
        // 所有的请求参数
        // 完整的请求链接
        sb.append("URL        : ").append(request.getMethod()).append(" ").append(request.getRequestURL()).append("\n");
        // 请求方的 ip地址
        sb.append("RequestIP  : ").append(UT.Web.getIpAddr(request)).append("\n");
        sb.append("Queries    : ")
        .append(UT.Json.toJSONString(request.getParameterMap())).append("\n");
        sb.append("Body       : ").append(bodyParser.getBodyIfNecessary(request)).append("\n");
        sb.append("Headers    : ").append(UT.Json.toJSONString(getHeders(request))).append("\n");
        sb.append("Session    : ")
                .append(UT.Json.toJSONString(getSessionInfo(request.getSession(false))))
                .append("\n");
        // Controller 的包名
        sb.append("Controller : ").append(h.getBean().getClass().getName())
        .append(" -> ").append(h.getMethod().getName()).append("()\n");
        if("/error".equals(request.getRequestURI())) {
            logger.warn("error", new Exception());
        }

        return sb.toString();
    }
    
    public static String getBodyString(HttpServletRequest request) {
        try (ServletInputStream in = request.getInputStream()) {
            if (in.isFinished()) {
                return null;
            }
            long contentLength = 0;
            String clen = request.getHeader(HttpHeaders.CONTENT_LENGTH);
            if (clen != null) {
                contentLength = Long.parseLong(clen);
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream(
                    contentLength > 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
            StreamUtils.copy(in, bos);
            
            String charEncoding = request.getCharacterEncoding() != null
                    ? request.getCharacterEncoding()
                    : "UTF-8";
            return bos.toString(charEncoding);
//            BufferedReader streamReader = new BufferedReader(new InputStreamReader(in, charEncoding));
//            String inputStr;
//            while ((inputStr = streamReader.readLine()) != null) {
//                responseStrBuilder.append(inputStr);
//            }
        } catch (Exception e) {
            logger.debug("warning:" + request.getRequestURI(), e);
            return null;
        }
    }

//    public static String paramsInfo(HttpServletRequest request) {
//        // 打印from格式的入参信息
//        Map<String, String[]> params = request.getParameterMap();
//        if (null != params && params.size() != 0) {
//            return UT.Json.toJSONString(params);
//        } else {
//            // 打印json格式的入参信息
//            String charEncoding = request.getCharacterEncoding() != null
//                    ? request.getCharacterEncoding()
//                    : "UTF-8";
//            StringBuilder responseStrBuilder = new StringBuilder();
//            try (InputStream in = request.getInputStream()) {
//                BufferedReader streamReader = new BufferedReader(
//                        new InputStreamReader(in, charEncoding));
//                String inputStr;
//                while ((inputStr = streamReader.readLine()) != null) {
//                    responseStrBuilder.append(inputStr);
//                }
//            } catch (Exception e) {
//                logger.debug("warning:", e);
//            }
//            return responseStrBuilder.toString();
//        }
//    }
//
//    public static Map<String, String> getAllRequestParameterNames(HttpServletRequest request) {
//        Map<String, String> map = new HashMap<>();
//        Enumeration<String> paramNames = request.getParameterNames();
//        while (paramNames.hasMoreElements()) {
//            String paramName = (String) paramNames.nextElement();
//            String[] paramValues = request.getParameterValues(paramName);
//            if (paramValues.length == 1) {
//                String paramValue = paramValues[0];
//                if (paramValue.length() != 0) {
//                    map.put(paramName, paramValue);
//                }
//
//            }
//        }
//        return map;
//    }

    @SuppressWarnings("unchecked")
    public static Map<String, Object> getSessionInfo(HttpSession session) {
        if (session == null) {
            return Collections.EMPTY_MAP;
        }
        StringBuilder info = new StringBuilder();
        info.append("Id_").append(session.getId());
        info.append(", ").append("CreationTime_").append(UT.Date.format_yyyy_MM_dd_HH_mm_ss(new Date(session.getCreationTime())));
        Map<String, Object> map = new HashMap<>();
        map.put("sessionInfo", info.toString());
        // 获取session中所有的键值
        Enumeration<String> enumeration = session.getAttributeNames();
        // 遍历enumeration中的
        while (enumeration.hasMoreElements()) {
            // 获取session键值
            String name = enumeration.nextElement();
            // 根据键值取session中的值
            Object value = session.getAttribute(name);
            map.put(name, value);
        }
        return map;
    }

    public static Map<String, String> getHeders(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        String key;
        while (headerNames.hasMoreElements()) {
            key = headerNames.nextElement();
            map.put(key, request.getHeader(key));
        }
        return map;
    }

    public static final String HEADER_SESSION_ID_NAME = "X-TOKEN";

    /**
     * 根据header判断，不是特别准确，遇到特殊情况再说吧。
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String httpMethod = request.getMethod().toUpperCase();
        // 默认“非GET请求都是AJAX请求，非AJAX请求都是GET请求”
        if (!httpMethod.equals("GET")) {
            return true;
        }

        String header = request.getHeader(HEADER_SESSION_ID_NAME);
        if (header != null) {
            return true;
        }
        String secFetchMode = request.getHeader("sec-fetch-mode");
        if (secFetchMode != null && secFetchMode.startsWith("cors")) {
            return true;
        }
        String accept = request.getHeader("accept");
        if (accept != null && accept.startsWith("application/json")) {
            return true;
        }
        return UT.Web.isAjaxRequest((HttpServletRequest) request);
    }
}
